;========================================================
; 已验证在 x86 实模式下可以跑通的通过 DMA 读取软盘数据的例程
;========================================================
app_lba_start equ 99
FDC_DOR         equ 0x3F2  ; 数字输出寄存器
FDC_MSR         equ 0x3F4  ; 主状态寄存器
FDC_FIFO        equ 0x3F5  ; 数据寄存器
FLPY_SECTORS_PER_TRACK equ 18
;===========================================
mov ax,0
mov ss,ax
mov sp,ax

mov ax, cs
add ax, 0x07c0
mov ds, ax

mov bx, msg         ; 起始位置
call put_string

; playground area

;先读取程序头部
xor di,di
mov si,app_lba_start
call read_floppy_disk_0

mov ecx, 1000000 ; 这个值根据 CPU 频率调整，模拟 1 秒
.delay:
    dec ecx
    jnz .delay          ; 循环直到 ECX 为 0
mov ax, [floppy_buffer]
mov dx, [floppy_buffer+2]
mov cx, 16
div cx
mov ds,ax
mov bx,dx
call put_string

hlt

;====================
read_floppy_disk_0:
    push ax
    push bx
    push cx
    push dx
    push ds
    push es

    ; 再计算柱面号 
    mov cx,FLPY_SECTORS_PER_TRACK*2
    mov ax,si
    mov dx,di
    div cx
    mov [@3],al

    ; 再计算磁头号
    mov cx,FLPY_SECTORS_PER_TRACK*2
    mov ax,si
    mov dx,di
    div cx
    mov ax,dx
    xor dx,dx
    mov cx,FLPY_SECTORS_PER_TRACK
    div cx
    mov [@3+1],al

    ; 计算扇区号
    mov cx,18
    mov ax,si
    mov dx,di
    div cx
    inc dx
    mov [@3+2],dl

    ; 设置 DMA
    call setup_dma
    ; 重置软盘控制器
    call reset_floppy
    ; 读取数据
    call read_sector

    pop es
    pop ds
    pop dx
    pop cx
    pop bx
    pop ax
    ret

setup_dma:
    ; 1. 禁用 DMA 通道 2
    mov dx, 0x0A       ; DMA 主屏蔽寄存器端口
    mov al, 0x04       ; 禁用通道 2
    out dx, al

    ; 2. 设置 DMA 地址寄存器
    mov dx, 0x04       ; 通道 2 的地址寄存器
    mov ax, [floppy_buffer]     ; 数据缓冲区偏移地址（低 16 位）
    out dx, al         ; 输出低 8 位
    mov al, ah         ; 高 8 位
    out dx, al

    ; 3. 设置 DMA 页寄存器
    mov dx, 0x81       ; 通道 2 的页寄存器
    mov ax, [floppy_buffer+2] ; 数据缓冲区段地址（高 8 位段地址）      
    out dx, al

    ; 4. 设置 DMA 传输长度（计数寄存器）
    mov dx, 0x05       ; 通道 2 的计数寄存器
    mov ax, 511        ; 传输字节数减 1（512 - 1 = 511）
    out dx, al         ; 输出低 8 位
    mov al, ah         ; 高 8 位
    out dx, al

    ; 5. 设置 DMA 模式寄存器
    mov dx, 0x0B       ; DMA 模式寄存器
    mov al, 0x56       ; 通道 2，读模式，单字节传输
    out dx, al

    ; 6. 启用 DMA 通道 2
    mov dx, 0x0A       ; DMA 主屏蔽寄存器
    mov al, 0x02       ; 启用通道 2
    out dx, al

; 该设置floppy了
reset_floppy:
    ; 发送复位命令
    mov dx, FDC_DOR
    mov al, 0x00       ; 复位软盘控制器
    out dx, al
    mov al, 0x1C       ; 重新启用软盘控制器和驱动器 0
    out dx, al

        ; 等待复位完成
    call wait_floppy_ready
    ret

    ; 等待软盘控制器就绪
    wait_floppy_ready:
        mov dx, FDC_MSR
    .wait:
        in al, dx
        test al, 0x80      ; 检查主状态寄存器的忙位（第 7 位）
        jz .wait          ; 如果忙，继续等待
        ret

; ============================
; 3. 读取软盘扇区
; ============================
read_sector:
    ; 发送 READ DATA 命令到软盘控制器
    mov dx, FDC_FIFO
    mov al, 0xE6       ; 命令：读数据
    out dx, al
    mov al, [@3+1]       ; 起始头号
    shl al, 2
    or  al, 0x00
    out dx, al
    mov al, [@3]       ; 起始磁道号
    out dx, al
    mov al, [@3+1]       ; 起始头号
    out dx, al
    mov al, [@3+2]       ; 起始扇区号（第2个扇区）
    out dx, al
    mov al, 0x02       ; 每扇区 512 字节
    out dx, al
    mov al, [@3+2]
    inc al
    out dx, al
    mov al, 0x1B       ; GAP3 长度
    out dx, al
    mov al, 0xFF       ; 数据长度（无关）
    out dx, al

    ; 等待操作完成
    call wait_floppy_ready

    ret 
; playground area end

; bx = 要输出的字符
; 输出字符串
put_string:
    mov ah, 0x0e  ; 0x10的0x0e命令是输出字符到屏幕，并推进光标
    mov al, [bx]  ; al为将要显示的字符
    cmp al, 0
    jz .return
    int 0x10
    inc bx
    jmp put_string
.return:
    ret

msg: db 'x86 asm playground',0x0d,0x0a
msg_end:
floppy_buffer: dd 0x10000
@3: db 0,0,0    ; 用于存储 柱面、磁头、扇区
times 510-($-$$) db 0
db 0x55,0xaa